package binchunk

import "io/ioutil"

type binchunk struct {
	header
	sizeUpvalues byte
	mainFunc     *Prototype
}

type header struct {
	signature       [4]byte // \x1b4c7561
	version         byte
	format          byte    //格式号
	luacData        [6]byte //19930D0A
	cintSize        byte
	csizetSize      byte
	instructionSize byte
	luaIntegerSize  byte
	luaNumberSize   byte
	luacInt         int64   //存储lua整数值 5678，判断大小端
	luacNum         float64 //存储370.5
}

const (
	LUA_SIGNATURE    = "\x1bLua"
	LUA_VERSION      = 0x53
	LUA_FORMAT       = 0
	LUAC_DATA        = "\x19\x93\r\n\x1A\n"
	CINT_SIZE        = 4
	CSIZET_SIZE      = 8
	INSTRUCTION_SIZE = 4
	LUA_INTEGER_SIZE = 8
	LUA_NUMBER_SIZE  = 8
	LUAC_INT         = 0x5678
	LUAC_NUM         = 370.5
)

type Prototype struct {
	Source          string //源文件名
	LineDefined     uint32 //起止行号，main是0
	LastLineDefined uint32
	NumParams       byte     //固定参数个数
	IsVarArg        byte     //是否有变长参数
	MaxStackSize    byte     //
	Code            []uint32 //指令表
	Constants       []interface{}
	UpValues        []UpValue
	Protos          []*Prototype //子函数原型表
	LineInfo        []uint32     //行号表
	LocVars         []LocVar     //局部变量表
	UpvalueNames    []string     //Upvalue名列表
}

const (
	TAG_NIL       = 0x00
	TAG_BOOLEAN   = 0x01
	TAG_NUMBER    = 0x03
	TAG_INTEGER   = 0x13
	TAG_SHORT_STR = 0x04
	TAG_LONG_STR  = 0x14
)

type UpValue struct {
	Instack byte
	Idx     byte
}

type LocVar struct {
	Varname string
	StartPC uint32
	EndPC   uint32
}

func Undump(data []byte) *Prototype {
	reader := &reader{data}
	reader.checkHeader()
	reader.readByte() // 代表的是 sizeUpvalues
	return reader.readProto("")
}

func (this *reader) readCode() []uint32 {
	codeNum := this.readUInt32()
	code := make([]uint32, codeNum)
	for i := range code {
		code[i] = this.readUInt32()
	}
	return code
}

func (this *reader) readConstants() []interface{} {
	constNum := this.readUInt32()
	constants := make([]interface{}, constNum)
	for i := range constants {
		constants[i] = this.readConst()
	}
	return constants
}

func (this *reader) readConst() interface{} {
	tag := this.readByte()
	switch tag {
	case TAG_NIL:
		return nil
	case TAG_BOOLEAN:
		return this.readByte() != 0
	case TAG_INTEGER:
		return this.readLuaInteger()
	case TAG_NUMBER:
		return this.readLuaNumber()
	case TAG_SHORT_STR:
		return this.readString()
	case TAG_LONG_STR:
		return this.readString()
	default:
		panic("read const tag error")
	}
}

func (this *reader) readUpvalues() []UpValue {
	upvalues := make([]UpValue, this.readUInt32())
	for i := range upvalues {
		upvalues[i] = UpValue{
			Instack: this.readByte(),
			Idx:     this.readByte(),
		}
	}
	return upvalues
}

func (this *reader) readProtos(source string) []*Prototype {
	protos := make([]*Prototype, this.readUInt32())
	for i := range protos {
		protos[i] = this.readProto(source)
	}
	return protos
}

func (this *reader) readLineInfo() []uint32 {
	lineinfos := make([]uint32, this.readUInt32())
	for i := range lineinfos {
		lineinfos[i] = this.readUInt32()
	}
	return lineinfos
}

func (this *reader) readLocVars() []LocVar {
	locVars := make([]LocVar, this.readUInt32())
	for i := range locVars {
		locVars[i] = LocVar{
			Varname: this.readString(),
			StartPC: this.readUInt32(),
			EndPC:   this.readUInt32(),
		}
	}
	return locVars
}

func (this *reader) readUpValueNames() []string {
	upvalueNames := make([]string, this.readUInt32())
	for i := range upvalueNames {
		upvalueNames[i] = this.readString()
	}
	return upvalueNames
}

func GetProtoFromFile(path string) *Prototype {
	data, err := ioutil.ReadFile(path)
	if err != nil {
		panic(err)
	}
	return Undump(data)
}
